package com.lipaluma.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lipaluma.annotations.config.ValidationStrategy;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserProcessException;
import com.lipaluma.exceptions.CsvParserValidationException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.merge.MergingTargetFunction;
import com.lipaluma.field.merge.TargetMergedAlgorithm;
import com.lipaluma.field.validator.strategies.ValidationStrategyEnum;
import com.lipaluma.io.SourceReader;
import com.lipaluma.io.SourceWriter;
import com.lipaluma.parser.post.treatment.unmarshall.AllLinesTreatmentProcessor;
import com.lipaluma.parser.post.treatment.unmarshall.merge.MergeLinesProcessor;
import com.lipaluma.xml.XmlMappings;

/**
 * Abstract Mapper
 * 
 * @see {@link ColumnDataParser}, {@link AbstractDataParserIterable}
 * 
 * @author Mario
 *
 * @param <TARGET>
 */
public abstract class AbstractAllDataParser<TARGET> extends AbstractDataParser<TARGET> implements DataParser<TARGET>{
	private static Logger LOG = LoggerFactory.getLogger(AbstractAllDataParser.class);
	
	protected List<AllLinesTreatmentProcessor<TARGET>> allLinesTreatmentProcessors = new ArrayList<AllLinesTreatmentProcessor<TARGET>>();
	protected ValidationStrategyEnum validationStrategy;
	
	protected AbstractAllDataParser() {}
	protected AbstractAllDataParser(AbstractAllDataParser.Builder<? extends AbstractAllDataParser<TARGET>, TARGET> builder) {
		super(builder);
		this.validationStrategy = builder.validationStrategy;
		MergeLinesProcessor<TARGET> processor;
		if(builder.mergingTargetFunction != null) {
			processor = new MergeLinesProcessor<TARGET>(builder.targetClass, builder.mergingTargetFunction);
		} else {
			processor = new MergeLinesProcessor<TARGET>(builder.targetClass, builder.targetMergeable);
		}
		if(processor.init(fieldMappers)) {
			allLinesTreatmentProcessors.add(processor);			
		}
		this.allLinesTreatmentProcessors.addAll(builder.processors);
	}

	protected AbstractAllDataParser(Class<TARGET> targetClass) {
		super(targetClass);
		this.validationStrategy = findValidationStrategy(targetClass);
		MergeLinesProcessor<TARGET> processor = new MergeLinesProcessor<TARGET>();
		if(processor.init(fieldMappers))
			allLinesTreatmentProcessors.add(processor);
	}
	
	protected AbstractAllDataParser(Class<TARGET> targetClass, XmlMappings xmlMappings) {
		super(targetClass, xmlMappings);
		this.validationStrategy = xmlMappings.getValidationStrategy();
		MergeLinesProcessor<TARGET> processor = new MergeLinesProcessor<TARGET>();
		if(processor.init(fieldMappers, xmlMappings))
			allLinesTreatmentProcessors.add(processor);
	}
	
	/**
	 * process All rows and returns the list of objects Mapped
	 * 
	 */
	protected List<TARGET> unmarshalAll(SourceReader fileReader) {
		try {
			List<TARGET> result = new ArrayList<TARGET>();
			StringBuilder errorMessage = new StringBuilder();
			while (fileReader.hasNext()) {
				TARGET target;
				try {
					target = unmarshal(fileReader.next());
					result.add(target);
				} catch (CsvParserValidationException e) {
					switch(validationStrategy) {
					case STOP_ON_FIRST_VALIDATION_ERROR:
						LOG.error("Validation Error [line "+fileReader.getCurrentLineNumber()+"] ", e);
						throw new CsvParserProcessException("Validation Error [line "+fileReader.getCurrentLineNumber()+"]", e);
					case LET_FINISH_AND_SHOW_ERRORS : 
						LOG.info("Validation Error [line "+fileReader.getCurrentLineNumber()+"] "+e.getMessage());
						errorMessage.append("Validation Error [line ").append(fileReader.getCurrentLineNumber()).append("]");
						errorMessage.append(e.getMessage());
						break;
					case FILTER_VALIDATION_ERRORS :
						LOG.warn("Validation Error [line "+fileReader.getCurrentLineNumber()+"]\n"+e.getMessage());
						LOG.warn("The line will be filtered.");
						errorMessage.append(">>>>>>>>>>>>>>> Validation Error [line ").append(fileReader.getCurrentLineNumber()).append("] <<<<<<<<<<<<<<<\n");
						errorMessage.append(e.getMessage()).append("\n");
					}
				}
			}
			if(StringUtils.isNotEmpty(errorMessage.toString())) {
				LoggerFactory.getLogger("result.error.file").error(errorMessage.toString());
				if(validationStrategy == ValidationStrategyEnum.LET_FINISH_AND_SHOW_ERRORS)
					throw new CsvParserProcessException("Error on process.\n"+errorMessage);
			}
			for(AllLinesTreatmentProcessor<TARGET> processor : allLinesTreatmentProcessors)
				result = processor.execute(result);
			return result;
		} finally {
			if(fileReader != null)
				fileReader.close();
		}
	}

	protected void marshalAll(Collection<TARGET> objects, SourceWriter writer) {
		//		List<ObjectMarshalled> objectsMarshalled = convertToObjectsMarshalled(objects);
		//		for (ObjectMarshalled objectMarshalled : objectsMarshalled) {
		//			writer.write(objectMarshalled);
		//		}
		try {
			if(objects == null)
				return;
			int objectNumber = 0;
			StringBuilder errorMessage = new StringBuilder();
			for (TARGET target : objects) {
				try {
					objectNumber++;
					marshal(target, writer);
				} catch (CsvParserValidationException e) {
					switch(validationStrategy) {
					case STOP_ON_FIRST_VALIDATION_ERROR:
						LOG.error("Validation Error [object number "+objectNumber+"]", e);
						writer.close();
						throw new CsvParserProcessException("Validation Error [line "+objectNumber+"]", e);
					case LET_FINISH_AND_SHOW_ERRORS : 
						LOG.info("Validation Error [object number "+objectNumber+"]"+e.getMessage());
						errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
						errorMessage.append(e.getMessage());
						break;
					case FILTER_VALIDATION_ERRORS :
						LOG.warn("Validation Error [object number "+objectNumber+"]"+e.getMessage());
						LOG.warn("The line will be filtered.");
						errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
						errorMessage.append(e.getMessage());
					}
					
				}
			}
			if(StringUtils.isNotEmpty(errorMessage.toString())) {
				LoggerFactory.getLogger("result.error.file").error(errorMessage.toString());
				if(validationStrategy == ValidationStrategyEnum.LET_FINISH_AND_SHOW_ERRORS)
					throw new CsvParserProcessException("Error on process.\n"+errorMessage);
			}
		} finally {
			writer.close();
		}
	}	

//	protected List<ObjectMarshalled> convertToObjectsMarshalled(Collection<TARGET> objects) {
//		if(objects == null)
//			throw new CsvParserException("The collection of objects cannot be null");
//
//		int objectNumber = 0;
//		StringBuilder errorMessage = new StringBuilder();
//		List<ObjectMarshalled> objectsMarshalled = new ArrayList<ObjectMarshalled>();
//		for (TARGET object : objects) {
//			try {
//				objectNumber++;
//				objectsMarshalled.add(marshal(object));
//			} catch (CsvParserValidationException e) {
//				switch(validationStrategy) {
//				case STOP_ON_FIRST_VALIDATION_ERROR:
//					LOG.error("Validation Error [object number "+objectNumber+"]", e);
//					throw new CsvParserProcessException("Validation Error [line "+objectNumber+"]", e);
//				case LET_FINISH_AND_SHOW_ERRORS : 
//					LOG.info("Validation Error [object number "+objectNumber+"]"+e.getMessage());
//					errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
//					errorMessage.append(e.getMessage());
//					break;
//				case FILTER_VALIDATION_ERRORS :
//					LOG.warn("Validation Error [object number "+objectNumber+"]"+e.getMessage());
//					LOG.warn("The line will be filtered.");
//					errorMessage.append("Validation Error [line ").append(objectNumber).append("]");
//					errorMessage.append(e.getMessage());
//				}
//			}
//		}
//		if(StringUtils.isNotEmpty(errorMessage.toString())) {
//			LoggerFactory.getLogger("result.error.file").error(errorMessage.toString());
//			if(validationStrategy == ValidationStrategyEnum.LET_FINISH_AND_SHOW_ERRORS)
//				throw new CsvParserProcessException("Error on process.\n"+errorMessage);
//		}
//		return objectsMarshalled;
//	}

	/**
	 * change the validation strategy
	 * @param strategy the validation strategy
	 */
	public void setValidationStrategy(ValidationStrategyEnum strategy) {
		this.validationStrategy = strategy;
	}

	/**
	 * read the validation strategy in the target entity with the annotation {@link ValidationStrategy}
	 * 
	 * @param clazz target entity
	 */
	private ValidationStrategyEnum findValidationStrategy(Class<TARGET> clazz) {
		ValidationStrategy strategy = clazz.getAnnotation(ValidationStrategy.class);
		if(strategy != null)
			return strategy.value();
		else
			return ValidationStrategyEnum.STOP_ON_FIRST_VALIDATION_ERROR;
	}

	public abstract static class Builder<PARSER extends AbstractAllDataParser<TARGET>, TARGET> extends AbstractDataParser.Builder<PARSER, TARGET> {
		private List<AllLinesTreatmentProcessor<TARGET>> processors = new ArrayList<AllLinesTreatmentProcessor<TARGET>>();
		private MergingTargetFunction mergingTargetFunction;
		private boolean targetMergeable;
		protected ValidationStrategyEnum validationStrategy = ValidationStrategyEnum.STOP_ON_FIRST_VALIDATION_ERROR;
		
		protected Builder(Class<TARGET> targetClass, Context context) {
			super(targetClass, context);
		}

		public Builder<PARSER, TARGET> withValidationStrategy(ValidationStrategyEnum validationStrategy) {
			this.validationStrategy = validationStrategy;
			return this;
		}
		
		@Override
		public Builder<PARSER, TARGET> withFieldMappers(List<FieldMapper> mappers) {
			super.withFieldMappers(mappers);
			return this;
		}

		public Builder<PARSER, TARGET> targetMergeable(boolean mergeable) {
			this.targetMergeable = true;
			return this;
		}

		public Builder<PARSER, TARGET> targetMergeableWith(TargetMergedAlgorithm algorithm, String... params) {
			this.targetMergeable = true;
			this.mergingTargetFunction = new MergingTargetFunction(algorithm, params);
			return this;
		}
		
		public Builder<PARSER, TARGET> addNewLinesTreatmentProcessor(AllLinesTreatmentProcessor<TARGET> processor) {
			processors.add(processor);
			return this;
		}
		
	}

}